﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using Renci.Data.Interop.OpenGIS.Exceptions;
using Renci.Data.Interop.OpenGIS.Ows_11;

namespace Renci.Ogc.Client
{
    /// <summary>
    /// Provides basic functionlity to comunicate with SWE web services 
    /// </summary>
    public class SweClient
    {
        /// <summary>
        /// Gets or sets the server URI.
        /// </summary>
        /// <value>The server URI.</value>
        public Uri ServerUri { get; protected set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="SweClient"/> class.
        /// </summary>
        /// <param name="uri">The uri of the web service to connect to.</param>
        public SweClient(Uri uri)
        {
            this.ServerUri = uri;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SweClient"/> class.
        /// </summary>
        /// <param name="uri">The uri of the web service to connect to.</param>
        public SweClient(string uri)
            : this(new Uri(uri))
        {
        }

        /// <summary>
        /// Contains XmlSerializers used by this class
        /// </summary>
        private Dictionary<Type, XmlSerializer> _serializers = new Dictionary<Type, XmlSerializer>();

        /// <summary>
        /// Holds ErrorReport class serializer to handle exceptions
        /// </summary>
        private XmlSerializer _exceptionSerializer = new XmlSerializer(typeof(ExceptionReport));

        /// <summary>
        /// Handles the web service POST request.
        /// </summary>
        /// <typeparam name="T">Type of request parameter.</typeparam>
        /// <typeparam name="R">Type of return parameter.</typeparam>
        /// <param name="serviceName">Name of the service.</param>
        /// <param name="requestName">Name of the request.</param>
        /// <param name="version">The version.</param>
        /// <param name="request">The request.</param>
        /// <param name="response">The response to be return by the service.</param>
        public void HandlePostRequest<TRequest, TResponse>(string serviceName, string requestName, string version, TRequest request, out TResponse response)
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(string.Format(CultureInfo.InvariantCulture, "{0}?service={1}&request={2}&version={3}", this.ServerUri.ToString(), serviceName, requestName, version)));
            webRequest.ContentType = "text/xml";
            webRequest.Method = "POST";
            webRequest.KeepAlive = true;

            using (Stream requestStream = webRequest.GetRequestStream())
            {
                //  Get cached serializer if exists or create one if it doesnt
                XmlSerializer serializer = null;
                Type requestType = typeof(TRequest);
                if (!this._serializers.ContainsKey(requestType))
                {
                    this._serializers.Add(requestType, new XmlSerializer(requestType));
                }

                serializer = this._serializers[requestType];

                serializer.Serialize(XmlWriter.Create(requestStream), request);
            }

            response = CreateResponse<TResponse>(webRequest.GetResponse() as HttpWebResponse);

        }

        /// <summary>
        /// Handles the web service GET request.
        /// </summary>
        /// <typeparam name="TRequest">The type of the request.</typeparam>
        /// <typeparam name="TResponse">The type of the response.</typeparam>
        /// <param name="serviceName">Name of the service.</param>
        /// <param name="requestName">Name of the request.</param>
        /// <param name="version">The version.</param>
        /// <param name="request">The request.</param>
        /// <param name="response">The response to be return by the service.</param>
        /// <param name="parameters">Additional service parameters.</param>
        public void HandleGetRequest<TResponse>(string serviceName, string requestName, string version, Dictionary<string, string> parameters, out TResponse response)
        {
            StringBuilder paramsNameValue = new StringBuilder();
            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    paramsNameValue.AppendFormat("&{0}={1}", HttpUtility.UrlEncode(parameter.Key), HttpUtility.UrlEncode(parameter.Value));
                }
            }

            string uri = string.Format(CultureInfo.InvariantCulture, "{0}?service={1}&request={2}&version={3}{4}", this.ServerUri.ToString(), serviceName, requestName, version, paramsNameValue.ToString());

            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(uri));
            webRequest.ContentType = "text/xml";
            webRequest.Method = "GET";
            webRequest.KeepAlive = true;

            response = CreateResponse<TResponse>(webRequest.GetResponse() as HttpWebResponse);
        }

        /// <summary>
        /// Creates the response object base on service response stream
        /// </summary>
        /// <typeparam name="TResponse">The type of the response.</typeparam>
        /// <param name="webResponse">The web response.</param>
        /// <returns></returns>
        private TResponse CreateResponse<TResponse>(HttpWebResponse webResponse)
        {
            StreamReader responseStream = new StreamReader(webResponse.GetResponseStream());
            string responseData = responseStream.ReadToEnd();

            if (string.IsNullOrEmpty(responseData))
            {
                throw new InvalidDataException("Server return no data.");
            }

            TResponse response;

            //  Get cached serializer if exists or create one if it doesnt
            XmlSerializer ser = null;
            Type responseType = typeof(TResponse);
            if (!this._serializers.ContainsKey(responseType))
            {
                this._serializers.Add(responseType, new XmlSerializer(responseType));
            }

            ser = this._serializers[responseType];

            if (ser.CanDeserialize(XmlReader.Create(new StringReader(responseData))))
            {
                response = (TResponse)ser.Deserialize(new StringReader(responseData));
            }
            else if (this._exceptionSerializer.CanDeserialize(XmlReader.Create(new StringReader(responseData))))
            {
                ExceptionReport errors = (ExceptionReport)this._exceptionSerializer.Deserialize(new StringReader(responseData));
                if (errors.Exceptions.Count > 0)
                {
                    ExceptionType exception = errors.Exceptions[0];
                    switch (exception.ExceptionCode)
                    {
                        case ExceptionCode.OperationNotSupported:
                            throw new OperationNotSupportedException(exception.Locator, exception.ExceptionText);
                        case ExceptionCode.MissingParameterValue:
                            throw new MissingParameterValueException(exception.Locator);
                        case ExceptionCode.InvalidParameterValue:
                            //  TODO:   Redesign exception handling, this exception will lose exception Locator property
                            throw new InvalidParameterValueException(exception.ExceptionText);
                        case ExceptionCode.VersionNegotiationFailed:
                            throw new VersionNegotiationException(exception.ExceptionText);
                        case ExceptionCode.InvalidUpdateSequence:
                            throw new InvalidUpdateSequenceException(exception.ExceptionText);
                        case ExceptionCode.OptionNotSupported:
                            throw new OptionNotSupportedException(exception.Locator);
                        case ExceptionCode.NoApplicableCode:
                            throw new NoApplicableCodeException(exception.ExceptionText);
                        default:
                            break;
                    }

                    throw new NoApplicableCodeException("Unknown exception.");
                }
                else
                {
                    throw new NoApplicableCodeException("Exception report does not have any exception.");
                }
            }
            else
            {
                throw new NoApplicableCodeException("Invalid service response.");
            }
            return response;
        }
    }
}
